package ws.performancetest.tests.synchronization.task;

import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import ws.performancetest.tests.synchronization.db.IDBRegister;
import ws.performancetest.tests.synchronization.lock.SharedList;

/**
 * This class creates tasks and execute them using a {@link ThreadPoolExecutor}.
 * 
 * @see AbstractTask.
 * 
 * @author Wilson Santos, 36228 <wilson.santos@ua.pt>
 */
public class TaskCreator {

  /** Pool for thread creation and task execution. */
  private final ThreadPoolExecutor poolExecutor;
  /** Number of times that each task created will be executed. */
  private int rep;
  /** List shared amongst tasks. */
  private SharedList list;
  /** Object used by the tasks to write data to the DB. */
  private IDBRegister dbregister;

  /**
   * Create a new instance.
   * 
   * @param poolExecutor Pool for thread creation and task execution.
   * @param rep Number of times that each task created will be executed.
   * @param minThreads minimum amount of threads that will be created during the tests.
   * @param maxThreads maximum amount of threads that will be created during the tests.
   */
  public TaskCreator(int rep, IDBRegister dbregister, int minThreads, int maxThreads) {
    this.poolExecutor = new ThreadPoolExecutor(
            minThreads, // corePoolSize, minimum number of created threads.
            maxThreads, // maximumPoolSize, maximum number of created threads.
            50L, // timeout, after this amount of time if the number of threads is greater than the core size,
            // any thread that finishes its task will exit.
            TimeUnit.SECONDS, // time unit used for the timeout
            new SynchronousQueue<Runnable>());  // queue used to store tasks that are waiting for an available thread.
    this.rep = rep;
    this.dbregister = dbregister;
  }

  /**
   * Create and execute a group of tasks.
   *
   * @param n Amount to be created and executed.
   * @param testId Test id that will be written to the DB.
   *               Each {@code testId} is shared among the same group of tasks.
   * @param taskClass Class name of the task ought to be created.
   *        <p>E.g. if you want to create an TWriter you should
   *          use {@code TWriter.class.getName()} for the
   *          task name.
   *        </p>
   */
  public void createTasks(int n, int testId, String taskClass) {
    AbstractTask task = null;

    while (n-- > 0) {
      try {
        /* Create the task and initialize its properties. */
        task = (AbstractTask) Class.forName(taskClass).newInstance();
        task.initialize(list, dbregister, rep, testId);
      } catch (Exception ex) {
        ex.printStackTrace();
        System.exit(1);
      }
      /* Run the task. */
      poolExecutor.execute(task);
    }
  }

  /**
   * Blocks until all tasks have completed execution.
   * 
   * @throws InterruptedException if interrupted while waiting.
   */
  public void waitForTasksToFinish() throws InterruptedException {
    poolExecutor.shutdown();
    /* the waiting time supplied should be enough for all tasks became completed. */
    poolExecutor.awaitTermination(1L, TimeUnit.HOURS);
  }

  /**
   * Set the list shared amongst tasks.
   *
   * @param list new shared list.
   */
  public void setList(SharedList list) {
    this.list = list;
  }
}
